package com.pig4cloud.pigx.user.wallet;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.google.protobuf.ByteString;
import com.pig4cloud.pigx.common.customize.enums.CommonDictEnum;
import com.pig4cloud.pigx.common.customize.redis.utils.RedisUtils;
import com.pig4cloud.pigx.common.customize.utils.id.IdWorker;
import com.pig4cloud.pigx.common.data.resolver.ParamResolver;
import com.pig4cloud.pigx.user.constant.RedisKeyPrefixConstant;
import com.pig4cloud.pigx.user.constant.WalletConstant;
import com.pig4cloud.pigx.user.dto.TronContractsEventsTransfer;
import com.pig4cloud.pigx.user.entity.*;
import com.pig4cloud.pigx.user.enums.RechargeRecordEnum;
import com.pig4cloud.pigx.user.enums.TransferEnums;
import com.pig4cloud.pigx.user.enums.UserEnums;
import com.pig4cloud.pigx.user.mapper.UserMapper;
import com.pig4cloud.pigx.user.service.*;
import io.reactivex.Flowable;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.tron.trident.core.ApiWrapper;
import org.tron.trident.utils.Base58Check;
import org.web3j.abi.EventEncoder;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.Address;
import org.web3j.abi.datatypes.Event;
import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameter;
import org.web3j.protocol.core.methods.request.EthFilter;
import org.web3j.protocol.core.methods.response.EthBlock;
import org.web3j.protocol.core.methods.response.EthBlockNumber;
import org.web3j.protocol.core.methods.response.EthLog;
import org.web3j.protocol.http.HttpService;
import org.web3j.utils.Convert;

import javax.annotation.Resource;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 项目启动初始化参数
 *
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class ApplicationRunnerImpl implements ApplicationRunner {

	private final RedisTemplate redis;

	private final OkHttpClient okHttpClient;

	private final CurrentBlockService currentBlockService;

	private final CurrencyService currencyService;

	private final UserAddressService addressService;

	private final RechargeRecordService rechargeRecordService;

	private final IdWorker idWorker;

	private final UserService userService;
	private final UserMapper userMapper;

	@Resource(name = "bscThreadPoolExecutor")
	private ThreadPoolExecutor bscThreadPoolExecutor;

	@Resource(name = "tronThreadPoolExecutor")
	private ThreadPoolExecutor tronThreadPoolExecutor;


	@Override
	public void run(ApplicationArguments args) {
		// tronThreadPoolExecutor.execute(this::tron);
		// bscThreadPoolExecutor.execute(this::bsc);
	}

	public void a() {
		userMapper.update(new User(){{
			setCommunityFuelRecharge(BigDecimal.ZERO);
		}},new QueryWrapper<User>(){{
			isNotNull("id");
		}});
		List<TransferDetailed> detailedList = new TransferDetailed().selectList(new QueryWrapper<TransferDetailed>() {{
			eq("type", TransferEnums.Type.TO_FUEL.getValue());
		}});
		for (TransferDetailed detailed : detailedList) {
			List<User> allParent = userService.getAllParent(detailed.getUserId());
			List<Long> longs = allParent.stream().map(User::getId).collect(Collectors.toList());
			userMapper.updateCommunityFuelRecharge(longs,detailed.getQuantity());
		}
	}

	public void bsc() {
		try {
			// 获取UNT 币种
			Currency currencyServiceOne = currencyService.getOne(new LambdaQueryWrapper<Currency>()
					.eq(Currency::getPublicChain, UserEnums.Chain.BSC.getValue())
					.eq(Currency::getName, WalletConstant.USDT)
					.eq(Currency::getState, CommonDictEnum.Status.NORMAL.getValue())
			);
			Web3j web3j = Web3j.build(new HttpService(currencyServiceOne.getNodeAddress()));
			// 连接USC-API
			// 获取区块
			CurrentBlock currentBlock = currentBlockService.getOne(new LambdaQueryWrapper<CurrentBlock>()
					.eq(CurrentBlock::getPublicChain, UserEnums.Chain.BSC.getValue())
			);
			// 初始化区块
			if (currentBlock == null) {
				org.web3j.protocol.core.Request<?, EthBlockNumber> ethBlockNumberRequest = web3j.ethBlockNumber();
				EthBlockNumber send;
				try {
					send = ethBlockNumberRequest.send();
				} catch (IOException e) {
					throw new RuntimeException(e);
				}
				BigInteger blockNumber = send.getBlockNumber();
				currentBlock = new CurrentBlock();
				currentBlock.setPublicChain(UserEnums.Chain.BSC.getValue());
				currentBlock.setBlockNumber(String.valueOf(blockNumber));
				currentBlockService.save(currentBlock);
			}

			final String[] blockNumber = {currentBlock.getBlockNumber()};

			Flowable<EthBlock> ethBlockFlowable = web3j.replayPastAndFutureBlocksFlowable(
					DefaultBlockParameter.valueOf(BigInteger.valueOf(Long.parseLong(blockNumber[0]))),
					false);

			CurrentBlock currentBlockUpdate = new CurrentBlock();
			currentBlockUpdate.setId(currentBlock.getId());

			ethBlockFlowable.blockingSubscribe(ethBlock -> {
				EthFilter filter = new EthFilter(ethBlock.getBlock().getHash(), currencyServiceOne.getSmartContractAddress());
				Event event = new Event("Transfer",
						Arrays.<TypeReference<?>>asList(
								new TypeReference<Address>(true) {
								},
								new TypeReference<Address>(true) {
								},
								new TypeReference<Uint256>(true) {
								}));
				filter.addSingleTopic(EventEncoder.encode(event));
				org.web3j.protocol.core.Request<?, EthLog> ethLogRequest = web3j.ethGetLogs(filter);
				EthLog send = ethLogRequest.send();
				List<EthLog.LogResult> logs = send.getLogs();
				if (CollUtil.isNotEmpty(logs)) {
					for (EthLog.LogResult logResult : logs) {
						EthLog.LogObject logObject = (EthLog.LogObject) logResult.get();
						// log.info("eth区块高度：{},交易记录：{}", ethBlock.getBlock().getNumber(), JSONUtil.parseObj(logObject));
						String toReplace = StrUtil.replace(logObject.getTopics().get(2), 2, 26, "");
						// 获取用户钱包地址
						List<UserAddress> list = addressService.list(new QueryWrapper<UserAddress>() {{
							eq("chain", UserEnums.Chain.BSC.getValue());
						}});
						if (CollUtil.isNotEmpty(list)) {
							List<String> addressList = list.stream().map(UserAddress::getAddress).collect(Collectors.toList());
							boolean contains = CollUtil.contains(addressList, toReplace);
							if (contains) {
								log.info("BSC地址匹配成功,交易哈希：{}", logObject.getTransactionHash());

								RechargeRecord recordServiceOne = rechargeRecordService.getOne(new LambdaQueryWrapper<RechargeRecord>()
										.eq(RechargeRecord::getTransactionHash, logObject.getTransactionHash())
								);

								if (recordServiceOne != null) {
									log.error("BSC存在相同交易哈希：{}", logObject.getTransactionHash());
									continue;
								}

								// 获取充值用户
								List<UserAddress> collect = list.stream().filter(address -> StrUtil.equals(address.getAddress(), toReplace)).collect(Collectors.toList());
								if (collect.size() < 1) {
									continue;
								}
								UserAddress userAddress = collect.get(0);
								// 获取充值数量
								String valueStr = new BigInteger(logObject.getData().substring(2), 16).toString();
								BigDecimal balance = Convert.fromWei(new BigDecimal(valueStr), Convert.Unit.ETHER).setScale(currencyServiceOne.getAccuracy(), RoundingMode.DOWN);
								// 充值记录
								RechargeRecord rechargeRecord = new RechargeRecord();
								rechargeRecord.setId(idWorker.nextId());
								// 公链
								rechargeRecord.setPublicChain(UserEnums.Chain.BSC.getValue());
								// 账户地址id
								rechargeRecord.setAddressId(userAddress.getId());
								// 用户id
								rechargeRecord.setUserId(userAddress.getUserId());
								// 区块
								rechargeRecord.setBlockNumber(String.valueOf(ethBlock.getBlock().getNumber()));
								// 充值数量
								rechargeRecord.setQuantity(balance);
								// 到账地址
								rechargeRecord.setArrivalAddress(toReplace);
								// 来源地址
								String fromReplace = StrUtil.replace(logObject.getTopics().get(1), 2, 26, "");
								rechargeRecord.setSourceAddress(fromReplace);
								// 区块确认数
								rechargeRecord.setBlockConfirmationNumber("0");
								// 交易哈希
								rechargeRecord.setTransactionHash(logObject.getTransactionHash());
								// 充值区块确认数
								rechargeRecord.setBlockConfirmationRechargeNumber(currencyServiceOne.getConfirmationBlockRechargeNumber());
								// 最小充值数
								rechargeRecord.setMinimumRechargeNumber(currencyServiceOne.getMinimumRechargeAmount());
								// 手续费
								rechargeRecord.setServiceCharge(currencyServiceOne.getServiceCharge());
								// 币种
								rechargeRecord.setCurrency(currencyServiceOne.getName());
								// 状态
								rechargeRecord.setState(RechargeRecordEnum.Status.RECHARGING.getValue());
								// 充值类型
								rechargeRecord.setType(RechargeRecordEnum.Type.CHAIN_RECHARGE.getValue());
								rechargeRecordService.save(rechargeRecord);
							}
						}
					}
				}
				currentBlockUpdate.setBlockNumber(String.valueOf(ethBlock.getBlock().getNumber()));
				currentBlockService.updateById(currentBlockUpdate);
			}, throwable -> {
				throw new RuntimeException(throwable.getMessage());
			});
		} catch (Exception e) {
			log.error("bsc-重置 error : " + e.getMessage());
			throw new RuntimeException();
		}
	}

	public void tron() {
		// 获取当前区块
		CurrentBlock currentBlock = currentBlockService.getOne(new LambdaQueryWrapper<CurrentBlock>()
				.eq(CurrentBlock::getPublicChain, UserEnums.Chain.TRON.getValue())
		);
		// 币种
		Currency currency = currencyService.getOne(new LambdaQueryWrapper<Currency>()
				// 公链
				.eq(Currency::getPublicChain, UserEnums.Chain.TRON.getValue())
				.eq(Currency::getName, WalletConstant.USDT)
				.eq(Currency::getState, CommonDictEnum.Status.NORMAL.getValue())
		);
		if (currentBlock == null) {

			currentBlock = new CurrentBlock();
			currentBlock.setPublicChain(UserEnums.Chain.TRON.getValue());
			currentBlock.setBlockNumber(String.valueOf(this.getLastBlockNumber(currency)));
			currentBlockService.save(currentBlock);
		}

		String blockNumber = currentBlock.getBlockNumber();
		CurrentBlock currentBlockUpdate = new CurrentBlock();
		currentBlockUpdate.setId(currentBlock.getId());
		do {
			try {
				if (this.getLastBlockNumber(currency).compareTo(new BigDecimal(blockNumber)) <= 0) {
					TimeUnit.MILLISECONDS.sleep(ParamResolver.getLong("TRON_BLOCK_READ_TIME_INTERVAL"));
					continue;
				}
				okhttp3.Request request = new Request.Builder()
						.url(currency.getNodeAddress() + "v1/contracts/" +
								currency.getSmartContractAddress() + "/events?event_name=Transfer&block_number=" +
								blockNumber + "&only_confirmed=true&limit=200")
						.get()
						.addHeader("accept", "application/json")
						.build();
				this.tronNext(request,currency);
				BigDecimal add = NumberUtil.add(blockNumber, "1");
				blockNumber = String.valueOf(add);
				currentBlockUpdate.setBlockNumber(String.valueOf(add));
				currentBlockService.updateById(currentBlockUpdate);

			} catch (InterruptedException e) {
				throw new RuntimeException(e);
			}
		} while (true);
	}

	public void tronNext(okhttp3.Request request,Currency currency) {
		Response response = null;
		try {
			response = okHttpClient.newCall(request).execute();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		String res = null;
		try {
			res = response.body().string();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		TronContractsEventsTransfer tronContractsEventsTransfer = JSONUtil.toBean(res, TronContractsEventsTransfer.class);
		if (tronContractsEventsTransfer.getData() == null) {
			return;
		}
		tronContractsEventsTransfer.getData().forEach(o -> {
			// 用户列表
			List<UserAddress> accountList = addressService.list(new LambdaQueryWrapper<UserAddress>()
					.eq(UserAddress::getChain, UserEnums.Chain.TRON.getValue())
			);
			if (CollUtil.isNotEmpty(accountList)) {
				// 获取所有用户地址
				List<String> addressList = accountList.stream().map(UserAddress::getAddress).collect(Collectors.toList());
				String toRes = o.getResult().getTo();
				String replace = StrUtil.replace(toRes, 0, 2, "41");
				ByteString bytes = ApiWrapper.parseAddress(replace);
				String tronAddress = Base58Check.bytesToBase58(bytes.toByteArray());

				if (CollUtil.contains(addressList, tronAddress)) {
					log.info("tron地址匹配成功,交易哈希：{}", o.getTransactionId());

					RechargeRecord recordServiceOne = rechargeRecordService.getOne(new LambdaQueryWrapper<RechargeRecord>()
							.eq(RechargeRecord::getTransactionHash, o.getTransactionId())
					);

					if (recordServiceOne != null) {
						log.error("tron存在相同交易哈希：{}", o.getTransactionId());
						return;
					}
					UserAddress userAddress = accountList.stream().filter(account -> StrUtil.equals(account.getAddress(), tronAddress)).findFirst().get();
					// 充值记录
					RechargeRecord rechargeRecord = new RechargeRecord();
					rechargeRecord.setId(idWorker.nextId());
					// 公链
					rechargeRecord.setPublicChain(UserEnums.Chain.TRON.getValue());
					// 账户地址id
					rechargeRecord.setAddressId(userAddress.getId());
					// 用户id
					rechargeRecord.setUserId(userAddress.getUserId());
					// 区块
					rechargeRecord.setBlockNumber(String.valueOf(o.getBlockNumber()));
					// 充值数量
					BigDecimal bigDecimal = NumberUtil.toBigDecimal(o.getResult().getValue()).movePointLeft(6);
					rechargeRecord.setQuantity(bigDecimal);
					// 到账地址
					rechargeRecord.setArrivalAddress(tronAddress);
					// 来源地址
					String fromRes = o.getResult().getFrom();
					String fromRep = StrUtil.replace(fromRes, 0, 2, "41");
					ByteString fromBytes = ApiWrapper.parseAddress(fromRep);
					String fromAddress = Base58Check.bytesToBase58(fromBytes.toByteArray());
					rechargeRecord.setSourceAddress(fromAddress);
					// 区块确认数
					rechargeRecord.setBlockConfirmationNumber("0");
					// 交易哈希
					rechargeRecord.setTransactionHash(o.getTransactionId());
					// 充值区块确认数
					rechargeRecord.setBlockConfirmationRechargeNumber(currency.getConfirmationBlockRechargeNumber());
					// 最小充值数
					rechargeRecord.setMinimumRechargeNumber(currency.getMinimumRechargeAmount());
					// 手续费
					rechargeRecord.setServiceCharge(currency.getServiceCharge());
					// 币种
					rechargeRecord.setCurrency(currency.getName());
					// 状态
					rechargeRecord.setState(RechargeRecordEnum.Status.RECHARGING.getValue());
					// 充值类型
					rechargeRecord.setType(RechargeRecordEnum.Type.CHAIN_RECHARGE.getValue());
					rechargeRecordService.save(rechargeRecord);

				}
			}

		});


	}

	/**
	 * 获取波场区块
	 * @param currency
	 * @return
	 */
	public BigDecimal getLastBlockNumber(Currency currency) {
		// 查询最新block
		okhttp3.Request request = new okhttp3.Request.Builder()
				.url(currency.getNodeAddress() + "walletsolidity/getnowblock")
				.get()
				.addHeader("Accept", "application/json")
				.build();

		Response response = null;
		try {
			response = okHttpClient.newCall(request).execute();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		String res = null;
		try {
			res = response.body().string();
			String blockNumber = JSONUtil.parseObj(res).getJSONObject("block_header").getJSONObject("raw_data").getStr("number");
			return new BigDecimal(blockNumber);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}


}
